﻿using arcsoft_usb_video.Entity;
using ArcSoftFace.Entity;
using ArcSoftFace.SDKModels;
using ArcSoftFace.SDKUtil;
using ArcSoftFace.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Configuration;
using System.Drawing;
using System.Windows.Forms;

namespace arcsoft_usb_video.ArcFace
{
    class ArcFaceSDK
    {
        private IntPtr pVideoFD = IntPtr.Zero;
        private IntPtr pVideoFR = IntPtr.Zero;
        private IntPtr pVideoFD_LIVE_RGB = IntPtr.Zero;
        private IntPtr pVideoFD_LIVE_IR = IntPtr.Zero;
        private IntPtr pImageFR = IntPtr.Zero;
        //人脸比对库
        private readonly ConcurrentDictionary<string, IntPtr> keyValuePairs = new ConcurrentDictionary<string, IntPtr>();
        private readonly object sycn_detect = new object();
        public void InitSDK()
        {
            int retCode;
            //读取配置文件
            AppSettingsReader reader = new AppSettingsReader();
            string appid = (string)reader.GetValue("appid", typeof(string));
            string appkey = (string)reader.GetValue("appkey", typeof(string));
            try
            {
                retCode = ASFFunctions.ASFActivation(appid, appkey);
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("无法加载 DLL"))
                {
                    MessageBox.Show("请将sdk相关DLL放入bin对应的x86或x64下的文件夹中!");
                }
                else
                {
                    MessageBox.Show("激活引擎失败!");
                }
                return;
            }

            //Video模式下检测脸部的角度优先值
            int videoDetectFaceOrientPriority = ASF_OrientPriority.ASF_OP_0_HIGHER_EXT;
            //人脸在图片中所占比例，如果需要调整检测人脸尺寸请修改此值，有效数值为2-32
            int detectFaceScaleVal = 16;
            //最大需要检测的人脸个数
            int detectFaceMaxNum = 5;

            //初始化视频模式下人脸检测引擎
            uint detectModeVideo = DetectionMode.ASF_DETECT_MODE_VIDEO;
            int combinedMaskVideo = FaceEngineMask.ASF_FACE_DETECT;
            retCode = ASFFunctions.ASFInitEngine(detectModeVideo, videoDetectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMaskVideo, ref pVideoFD);
            Console.WriteLine("Init 人脸追踪 pVideoFD Result:" + retCode);

            combinedMaskVideo = FaceEngineMask.ASF_FACE_DETECT | FaceEngineMask.ASF_FACERECOGNITION;
            retCode = ASFFunctions.ASFInitEngine(detectModeVideo, videoDetectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMaskVideo, ref pVideoFR);
            Console.WriteLine("Init 人脸识别 pVideoFR Result:" + retCode);

            combinedMaskVideo = FaceEngineMask.ASF_FACE_DETECT | FaceEngineMask.ASF_LIVENESS;
            retCode = ASFFunctions.ASFInitEngine(detectModeVideo, videoDetectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMaskVideo, ref pVideoFD_LIVE_RGB);
            Console.WriteLine("Init RGB活体 pVideoFD_LIVE_RGB Result:" + retCode);

            combinedMaskVideo = FaceEngineMask.ASF_FACE_DETECT | FaceEngineMask.ASF_IR_LIVENESS;
            retCode = ASFFunctions.ASFInitEngine(detectModeVideo, videoDetectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMaskVideo, ref pVideoFD_LIVE_IR);
            Console.WriteLine("Init IR活体 pVideoFD_LIVE_IR Result:" + retCode);

            detectModeVideo = DetectionMode.ASF_DETECT_MODE_IMAGE;
            videoDetectFaceOrientPriority = ASF_OrientPriority.ASF_OP_0_ONLY;
            combinedMaskVideo = FaceEngineMask.ASF_FACE_DETECT | FaceEngineMask.ASF_FACERECOGNITION;
            retCode = ASFFunctions.ASFInitEngine(detectModeVideo, videoDetectFaceOrientPriority, detectFaceScaleVal, detectFaceMaxNum, combinedMaskVideo, ref pImageFR);
            Console.WriteLine("Init 图片识别 pImageFR Result:" + retCode);
        }

        public Rectangle toRect(MRECT rect)
        {
            return new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
        }

        public KeyValuePair<int, Rectangle>[] MultiFaceInfoToKV(ASF_MultiFaceInfo multiFaceInfo) {
            if(multiFaceInfo.faceNum == 0)
            {
                return new KeyValuePair<int, Rectangle>[0];
            }

            var kvps = new KeyValuePair<int, Rectangle>[multiFaceInfo.faceNum];
            for (int i = 0; i < multiFaceInfo.faceNum; i++)
            {
                try
                {
                    MRECT rect = MemoryUtil.PtrToStructure<MRECT>(multiFaceInfo.faceRects + MemoryUtil.SizeOf<MRECT>() * i);
                    Int32 faceId = MemoryUtil.PtrToStructure<int>(multiFaceInfo.faceID + MemoryUtil.SizeOf<int>() * i);
                    kvps[i] = new KeyValuePair<int, Rectangle>(faceId, toRect(rect));
                }
                catch (Exception ex)
                {
                    Console.WriteLine("人脸追踪异常：{0}", ex.Message);
                }
            }

            return kvps;
        }

        public ASF_SingleFaceInfo[] MultiFaceInfoToArr(ASF_MultiFaceInfo multiFaceInfo)
        {
            if(multiFaceInfo.faceNum == 0)
            {
                return new ASF_SingleFaceInfo[0];
            }

            ASF_SingleFaceInfo[] arr = new ASF_SingleFaceInfo[multiFaceInfo.faceNum];
            for (int i = 0; i < arr.Length; i++)
            {
                ASF_SingleFaceInfo single = new ASF_SingleFaceInfo();
                single.faceRect = MemoryUtil.PtrToStructure<MRECT>(multiFaceInfo.faceRects + MemoryUtil.SizeOf<MRECT>() * i);
                single.faceOrient = MemoryUtil.PtrToStructure<int>(multiFaceInfo.faceOrients + MemoryUtil.SizeOf<int>() * i);
                arr[i] = single;
            }

            return arr;
        }

        public ASF_MultiFaceInfo DetectFaces(Image image)
        {
            if (image == null || image.Width == 0)
            {
                return new ASF_MultiFaceInfo();
            }

            ImageInfo imageInfo = ImageUtil.ReadBMP(image);
            ASF_MultiFaceInfo multiFaceInfo = DetectFaces(imageInfo);
            MemoryUtil.Free(imageInfo.imgData);
            return multiFaceInfo;
        }

        public ASF_MultiFaceInfo DetectFaces(ImageInfo imageInfo)
        {
            ASF_MultiFaceInfo multiFaceInfo = new ASF_MultiFaceInfo();
            IntPtr ptr_multiFaceInfo = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_MultiFaceInfo>());
            MemoryUtil.StructureToPtr(multiFaceInfo, ptr_multiFaceInfo);

            lock (sycn_detect)
            {
                ASFFunctions.ASFDetectFaces(pVideoFD, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, ptr_multiFaceInfo);
            }

            ASF_MultiFaceInfo ret = MemoryUtil.PtrToStructure<ASF_MultiFaceInfo>(ptr_multiFaceInfo);
            MemoryUtil.Free(ptr_multiFaceInfo);

            return ret;
        }

        public FaceInfo[] SearchFace(Image image)
        {
            IntPtrCloser ptrCloser = new IntPtrCloser();

            try
            {
                ImageInfo imageInfo = ImageUtil.ReadBMP(image);
                ptrCloser.Register(imageInfo.imgData);
                ASF_MultiFaceInfo multiFaceInfo = DetectFaces(imageInfo);

                FaceInfo[] arr = new FaceInfo[multiFaceInfo.faceNum];
                for (int i = 0; i < multiFaceInfo.faceNum; i++)
                {
                    ASF_SingleFaceInfo singleFaceInfo = new ASF_SingleFaceInfo();

                    singleFaceInfo.faceRect = MemoryUtil.PtrToStructure<MRECT>(multiFaceInfo.faceRects + MemoryUtil.SizeOf<MRECT>() * i);
                    singleFaceInfo.faceOrient = MemoryUtil.PtrToStructure<int>(multiFaceInfo.faceOrients + MemoryUtil.SizeOf<int>() * i);
                    int faceId = MemoryUtil.PtrToStructure<int>(multiFaceInfo.faceID + MemoryUtil.SizeOf<int>() * i);

                    IntPtr pFaceFeature = ptrCloser.Register(MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_FaceFeature>()));
                    IntPtr pSingleFaceInfo = ptrCloser.Register(MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_SingleFaceInfo>()));
                    MemoryUtil.StructureToPtr(singleFaceInfo, pSingleFaceInfo);
                    int retCode = ASFFunctions.ASFFaceFeatureExtract(pVideoFR, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pSingleFaceInfo, pFaceFeature);

                    if (retCode != 0) continue;

                    float similarity = 0;
                    foreach (var kvp in keyValuePairs)
                    {
                        similarity = 0;
                        retCode = ASFFunctions.ASFFaceFeatureCompare(pVideoFR, pFaceFeature, kvp.Value, ref similarity);

                        if (retCode != 0 || similarity < 0.8) continue;

                        FaceInfo faceInfo = new FaceInfo();
                        faceInfo.key = kvp.Key;
                        faceInfo.rectangle = toRect(singleFaceInfo.faceRect);
                        arr[i] = faceInfo;
                        break;
                    }

                }

                return arr;
            }
            finally
            {
                ptrCloser.Close();
            }
        }


        public bool AddFace(String key, byte[] data)
        {
            IntPtr intPtr;
            keyValuePairs.TryRemove(key, out intPtr);
            ASF_FaceFeature faceFeature = new ASF_FaceFeature
            {
                featureSize = data.Length,
                feature = MemoryUtil.Malloc(data.Length)
            };
            MemoryUtil.Copy(data, 0, faceFeature.feature, faceFeature.featureSize);

            IntPtr pFaceFeature = MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_FaceFeature>());
            MemoryUtil.StructureToPtr(faceFeature, pFaceFeature);

            keyValuePairs.TryAdd(key, pFaceFeature);
            Console.WriteLine("添加用户到对比库：{0}", key);
            return true;
        }


        public bool Live(Image bitmap)
        {
            IntPtrCloser intPtrCloser = new IntPtrCloser();
            try
            {

                ImageInfo imageInfo = ImageUtil.ReadBMP(bitmap);
                intPtrCloser.Register(imageInfo.imgData);

                ASF_MultiFaceInfo multiFaceInfo = DetectFaces(imageInfo);
                if (multiFaceInfo.faceNum == 0) return false;

                IntPtr pMultiFaceInfo = intPtrCloser.Register(MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_MultiFaceInfo>()));
                MemoryUtil.StructureToPtr<ASF_MultiFaceInfo>(multiFaceInfo, pMultiFaceInfo);

                int retCode = ASFFunctions.ASFProcess(pVideoFD_LIVE_RGB, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pMultiFaceInfo, FaceEngineMask.ASF_LIVENESS);

                if (retCode != 0) return false;

                IntPtr pLivenessInfo = intPtrCloser.Register(MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_LivenessInfo>()));
                retCode = ASFFunctions.ASFGetLivenessScore(pVideoFD_LIVE_RGB, pLivenessInfo);

                if (retCode != 0) return false;

                ASF_LivenessInfo livenessInfo = MemoryUtil.PtrToStructure<ASF_LivenessInfo>(pLivenessInfo);

                if (livenessInfo.num == 0) return false;

                bool isLive = true;
                for (int i = 0; i < livenessInfo.num; i++)
                {
                    isLive = isLive && (MemoryUtil.PtrToStructure<int>(livenessInfo.isLive + MemoryUtil.SizeOf<int>() * i)) == 1;
                }

                return isLive;
            }
            finally
            {
                intPtrCloser.Close();
            }
        }

        public byte[] FaceFeatureData(Image image)
        {
            IntPtrCloser intPtrCloser = new IntPtrCloser();

            try
            {
                ImageInfo imageInfo = ImageUtil.ReadBMP(image);
                intPtrCloser.Register(imageInfo.imgData);

                IntPtr pFaceFeature = intPtrCloser.Register(MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_FaceFeature>()));
                ASF_MultiFaceInfo multiFaceInfo = DetectFaces(imageInfo);

                ASF_SingleFaceInfo singleFaceInfo = new ASF_SingleFaceInfo();
                singleFaceInfo.faceRect = MemoryUtil.PtrToStructure<MRECT>(multiFaceInfo.faceRects);
                singleFaceInfo.faceOrient = MemoryUtil.PtrToStructure<int>(multiFaceInfo.faceOrients);
                IntPtr pSingleFaceInfo = intPtrCloser.Register(MemoryUtil.Malloc(MemoryUtil.SizeOf<ASF_SingleFaceInfo>()));
                MemoryUtil.StructureToPtr(singleFaceInfo, pSingleFaceInfo);

                int retCode = ASFFunctions.ASFFaceFeatureExtract(pImageFR, imageInfo.width, imageInfo.height, imageInfo.format, imageInfo.imgData, pSingleFaceInfo, pFaceFeature);

                ASF_FaceFeature faceFeature = MemoryUtil.PtrToStructure<ASF_FaceFeature>(pFaceFeature);
                byte[] feature = new byte[faceFeature.featureSize];
                MemoryUtil.Copy(faceFeature.feature, feature, 0, faceFeature.featureSize);

                return feature;
            }
            finally
            {
                intPtrCloser.Close();
            }
        }
    }
}
